/**
 * Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
 * This file is part of CSipSimple.
 *
 *  CSipSimple is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  If you own a pjsip commercial license you can also redistribute it
 *  and/or modify it under the terms of the GNU Lesser General Public License
 *  as an android library.
 *
 *  CSipSimple is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with CSipSimple.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.cloudptt.api.product;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

import com.cloudptt.api.product.log.Log;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.provider.Settings;
import android.text.TextUtils;

public class NetWorkBroadcast extends BroadcastReceiver {

	private static final String THIS_FILE = "NetWorkBroadcast";

	private Context ctx = null;
	private NetworkListener lister = null;

	private String mNetworkType;
	private boolean isConnected = false;

	private String mRoutes = "";
	private String mBSSID = "";

	public NetWorkBroadcast(Context ctx,NetworkListener lister) 
	{
		Log.d(THIS_FILE, "NetWorkBroadcast");
		this.ctx = ctx;
		this.lister = lister;

		ConnectivityManager cm =
				(ConnectivityManager)ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
		NetworkInfo info = cm.getActiveNetworkInfo();
		isConnected = (info != null && info.isConnected());
		mNetworkType = isConnected ? info.getTypeName() : "null";

		Log.d(THIS_FILE, "NetWorkBroadcast mConnected = " + isConnected + " mNetworkType = " + mNetworkType);
		this.ctx.registerReceiver(this, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
		this.ctx.registerReceiver(this, new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
	}

	public boolean checkNetWork()
	{
		return isConnected;
	}

	@Override
	public void onReceive(final Context context, final Intent intent) 
	{
		String action = intent.getAction();
		if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
			ConnectivityManager cm =
					(ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
			NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
			isConnected = (activeNetwork != null && activeNetwork.isConnected());
			onConnectivityChanged(isConnected,activeNetwork);
//			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//				Network network = cm.getActiveNetwork();
//				NetworkCapabilities capabilities = cm.getNetworkCapabilities(network);
//				isConnected = capabilities.hasCapability(NetworkCapabilities.TRANSPORT_WIFI) ?: false
//			} else {
//				NetworkInfo networkInfo = cm.activeNetworkInfo;
//				isConnected = (info != null && info.isConnected());
//			}
		}
		else if (intent.getAction().equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
			boolean isEnabled = intent.hasExtra(Settings.System.AIRPLANE_MODE_ON) &&
					intent.getBooleanExtra(Settings.System.AIRPLANE_MODE_ON, false);
			Log.d(THIS_FILE, "NetWorkBroadcast ACTION_AIRPLANE_MODE_CHANGED isEnabled = " + isEnabled);
			if(lister != null)
			{
				lister.networkChange(!isEnabled);
			}
		}
	}


	private static final String PROC_NET_ROUTE = "/proc/net/route";
	private String dumpRoutes() {
		String routes = "";
		FileReader fr = null;
		try {
			fr = new FileReader(PROC_NET_ROUTE);
			if(fr != null) {
				StringBuffer contentBuf = new StringBuffer();
				BufferedReader buf = new BufferedReader(fr);
				String line;
				while ((line = buf.readLine()) != null) {
					contentBuf.append(line+"\n");
				}
				routes = contentBuf.toString();
				buf.close();
			}
		} catch (FileNotFoundException e) {
			Log.e(THIS_FILE, "No route file found routes", e);
		} catch (IOException e) {
			Log.e(THIS_FILE, "Unable to read route file", e);
		}finally {
			try {
				if(fr != null)
				{
					fr.close();
				}
			} catch (IOException e) {
				Log.e(THIS_FILE, "Unable to close route file", e);
			}
		}

		// Clean routes that point unique host 
		// this aims to workaround the fact android 4.x wakeup 3G layer when position is retrieve to resolve over 3g position
		String finalRoutes = routes;
		if(!TextUtils.isEmpty(routes)) {
			String[] items = routes.split("\n");
			List<String> finalItems = new ArrayList<String>();
			int line = 0;
			for(String item : items) {
				boolean addItem = true;
				if(line > 0){
					String[] ent = item.split("\t");
					if(ent.length > 8) {
						String maskStr = ent[7];
						if(maskStr.matches("^[0-9A-F]{8}$")) {
							int lastMaskPart = Integer.parseInt(maskStr.substring(0, 2), 16);
							if(lastMaskPart > 192) {
								// if more than 255.255.255.192 : ignore this line
								addItem = false;
							}
						}else {
							Log.w(THIS_FILE, "The route mask does not looks like a mask" + maskStr);
						}
					}
				}

				if(addItem) {
					finalItems.add(item);
				}
				line ++;
			}
			finalRoutes = TextUtils.join("\n", finalItems); 
		}

		return finalRoutes;
	}


	/**
	 * Treat the fact that the connectivity has changed
	 * @param info Network info
	 * @param incomingOnly start only if for outgoing 
	 * @throws SameThreadException
	 */
	private void onConnectivityChanged(boolean isConnected, NetworkInfo info)
	{
		if(info != null)
		{
			Log.d(THIS_FILE, "onConnectivityChanged NetworkInfo is " + info.toString());
		}
		else
		{
			Log.d(THIS_FILE, "onConnectivityChanged NetworkInfo is null");
		}
		if (info == null || info.isConnected() ||
				!info.getTypeName().equals(mNetworkType)) {
			ConnectivityManager cm = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
			info = cm.getActiveNetworkInfo();
		}

		//        boolean connected = (info != null && info.isConnected() && service.isConnectivityValid());
		boolean connected = (info != null && info.isConnected());
		String networkType = connected ? info.getTypeName() : "null";
		String currentRoutes = dumpRoutes();
		String oldRoutes;
		synchronized (mRoutes) {
			oldRoutes = mRoutes;
		}

		Log.d(THIS_FILE, "OLD Connected = "+ isConnected + " networkType = " + mNetworkType + " Routes= "+ oldRoutes);
		Log.d(THIS_FILE, "NEW Connected = "+ connected + " networkType = " + networkType + " Routes= "+ currentRoutes);

		String oldBSSID= mBSSID;
		String currentBSSID = "";
		if(info != null && connected)
		{
			WifiManager mWifi = (WifiManager) ctx.getSystemService(Context.WIFI_SERVICE);
			WifiInfo wifiInfo = mWifi.getConnectionInfo();
			if(wifiInfo != null)
			{
				currentBSSID = wifiInfo.getBSSID() == null ? "" : wifiInfo.getBSSID();
				Log.e(THIS_FILE, "getBSSID="+currentBSSID+",getSSID="+wifiInfo.getSSID());
			}
		}
		// Ignore the event if the current active network is not changed.
		if (connected == isConnected && networkType.equals(mNetworkType) && 
				currentRoutes.equals(oldRoutes) && currentBSSID.equals(oldBSSID) ) 
		{
			Log.d(THIS_FILE, "same network return");
			return;
		}
		if(Log.getLogLevel() >= 4) {
			if(!networkType.equals(mNetworkType)) {
				Log.d(THIS_FILE, "onConnectivityChanged(): " + mNetworkType +
						" -> " + networkType);
			}else {
				Log.d(THIS_FILE, "Route changed : "+ mRoutes+" -> "+currentRoutes);
			}
		}
		// Now process the event
		synchronized (mRoutes) 
		{
			mRoutes = currentRoutes;
		}
		isConnected = connected;
		mNetworkType = networkType;
		Log.d(THIS_FILE, "mNetworkType = " + mNetworkType + " networkType = " + networkType);
		mBSSID = currentBSSID;

		Log.d(THIS_FILE, "networkChange connected = " + connected);
		if(lister != null)
		{
			lister.networkChange(connected);
		}
	}

	public boolean isConnected() {
		return isConnected;
	}

	public void clearResource()
	{
		Log.d(THIS_FILE, "NetWorkBroadcast clearResource");
		this.ctx.unregisterReceiver(this);
		this.ctx = null;
		this.lister = null;
		this.mNetworkType = null;
		this.mRoutes = null;
		this.mBSSID = null;
	}


	public static String getIpAddressString() {
		try {
			for (Enumeration<NetworkInterface> enNetI = NetworkInterface
					.getNetworkInterfaces(); enNetI.hasMoreElements(); ) {
				NetworkInterface netI = enNetI.nextElement();
				for (Enumeration<InetAddress> enumIpAddr = netI
						.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
					InetAddress inetAddress = enumIpAddr.nextElement();
					if (inetAddress instanceof Inet4Address && !inetAddress.isLoopbackAddress()) {
						return inetAddress.getHostAddress();
					}
				}
			}
		} catch (SocketException e) {
			e.printStackTrace();
		}
		return "0.0.0.0";
	}
}